#include "Hash.h"


Hash::~Hash() {
}


Hash::Hash(string nombre, unsigned int tamanioBucket){

	this->tamanioBucket=tamanioBucket;

	this->cantidadBuckets = 4; //ponerlo en constante
	this-> archivo = new ArchivoDeBloques(nombre,cantidadBuckets,tamanioBucket);
	//por defecto si inicializa en un bucket despues le puedo agregar mas
	//esto lo tengo que cambiar por un archivo de registros fijos
	this-> archivoBLibres = new BloquesLibres(nombre +"lib");
	this->archivoBLibres->guardarBloques();
	tabla=new Tabla(cantidadBuckets);
	this->narchivoTabla=nombre + "tab";
	this->tabla->guardarTabla(narchivoTabla);
}


bool Hash::insertar(Key key){

	unsigned int posBucket=tabla->obtenerNroBucket(key);
	unsigned int posTabla=tabla->calcularNroBucket(key);
	Bloque* bloque=archivo->leerBloque(posBucket);

	Bucket bucket =Bucket(this->tamanioBucket, bloque);
	Bloque * bloqueviejo=new Bloque(tamanioBucket);
	Bucket bucketnuevo=Bucket(tamanioBucket,bloqueviejo);
	unsigned int nroBloque;

	//verifico que no exista
	if (!bucket.existe(key)){
		//verifico que no desborde
		if (!bucket.desborda(key)){
			bucket.insertar(key,tabla->obtenertd(posBucket));
			archivo->escribirBloque(*bucket.obtenerBloque(),posBucket);
		}else { //no entra ver desbord
			//verifico si tengo que dcuplicar la tabla td= tt
			if (bucket.obtenerTamanioDispersion()==int(tabla->tamanioTabla())){
				tabla->duplicarTabla();
			}
			//busco un bloque libre para insertar
			if(!archivoBLibres->vacio()){

					nroBloque=archivoBLibres->obteneBloque();
					this->archivoBLibres->guardarBloques();
					Bloque* bloqueLibre= new Bloque(tamanioBucket);
					Bucket bucketnuevo=Bucket(tamanioBucket,bloqueLibre);
					tabla->modificar(posTabla,nroBloque);

			}else {//si no hay bloques libres agrego un bloque al final
					archivo->agregarBloquesVacios(1);

					//hasta que esto ande
					nroBloque=archivo->cantBloques-1;
					cantidadBuckets++;
					Bloque* bloque=new Bloque(tamanioBucket);
					bucketnuevo=Bucket(tamanioBucket,bloque);
					archivo->escribirBloque(*bloque,nroBloque);
					tabla->modificar(posTabla,nroBloque);
			}

		unsigned int tam=bucket.obtenerVector().size();
		Bloque * bloqueviejo=new Bloque(tamanioBucket);
		Bucket bucketviejo=Bucket(tamanioBucket,bloqueviejo);

		//reparto lo viejo en los dos bloques nuevos
		for(unsigned int x=0;x<tam;x++){
			Key* keyIns=(bucket.obtenerVector())[x];
			unsigned int posBucketNueva=tabla->obtenerNroBucket(*keyIns);
			//tabla->mostrar();
			if (posBucketNueva==nroBloque){
				bucketnuevo.insertar(*keyIns,tabla->obtenertd(nroBloque));
			}else bucketviejo.insertar(*keyIns,tabla->obtenertd(posBucket));
		}
		//inserto la nueva
		unsigned int posBucketNueva=tabla->obtenerNroBucket(key);
		if (posBucketNueva==nroBloque){
			bucketnuevo.insertar(key,tabla->obtenertd(nroBloque));
		}
			else bucketviejo.insertar(key,tabla->obtenertd(posBucket));
			//escribo en el archivo los bloques
			archivo->escribirBloque(*bucketnuevo.obtenerBloque(),nroBloque);
			archivo->escribirBloque(*bucketviejo.obtenerBloque(),posBucket);
			this->tabla->guardarTabla(narchivoTabla);
			return true;
		}
		this->tabla->guardarTabla(narchivoTabla);
		return true;
	}else return false;//existe la key a insertar
}


void Hash::mostrar(){
	cout<<endl<<"Contenido de los bloques del arhivo:"<<endl<<endl;

	for(unsigned int x=0;x<cantidadBuckets;x++){

		cout<<"Bloque: "<<x<<"  :  "<<archivo->leerBloque(x)->bufferBloque<<endl;
	}

	cout<<endl<<"Contenido de la tabla de hash:"<<endl;
	tabla->mostrar();

	cout<<endl<<"Contenido del archivo de bloques libres:"<<endl;
	archivoBLibres->mostrar();
	cout<<endl;
}

bool Hash::existe(Key key){

	unsigned int posBucket=tabla->obtenerNroBucket(key);
	Bloque* bloque=archivo->leerBloque(posBucket);
	Bucket bucket =Bucket(this->tamanioBucket, bloque);
	if (bucket.existe(key)) return true;
	else return false;
}

bool Hash::eliminar(Key key){
	if(existe(key)){
		unsigned int posBucket=tabla->obtenerNroBucket(key);
		unsigned int posTabla=tabla->calcularNroBucket(key);
		Bloque* bloque=archivo->leerBloque(posBucket);
		Bucket bucket =Bucket(this->tamanioBucket, bloque);

		//si hay mas de una key en el bucket elimino key, y bucket no queda vacio
		if (bucket.obtenerCantidadKeys()>1){
			bucket.borrar(key, tabla->obtenertd(posBucket));
			archivo->escribirBloque(*bucket.obtenerBloque(),posBucket);
		}//si tiene un solo elemento, al eliminar queda vacio
		else {

			//si queda vacio y su td es igual al tamanio de la tabla
			if (bucket.obtenerTamanioDispersion()==int(tabla->tamanioTabla())){
				//bucket.borrar(key,tabla->obtenertd(posBucket));
				archivo->escribirBloque(*bucket.obtenerBloque(),posBucket);
				this->archivoBLibres->insertarBloque(posBucket);
				this->archivoBLibres->guardarBloques();
				//se inspecciona la tabla tamanio tabla/2 posiciones adelante y atras
				int posiciones=tabla->tamanioTabla()/2;
				int posTabla1=posTabla-posiciones;
				if (posTabla1 <0) posTabla1=posTabla1+tabla->tamanioTabla();
				 int posTabla2=posTabla+posiciones;
				if (posTabla2 >=int(tabla->tamanioTabla())) posTabla2=posTabla2-int(tabla->tamanioTabla());
				//comparo el contenido de la tabla si es igual, reduzco la tabla
				if (tabla->obtener(posTabla1)==tabla->obtener(posTabla2)){
				tabla->modificar(posTabla,tabla->obtener(posTabla1));

				Bloque *bloque1=archivo->leerBloque(tabla->obtener(posTabla2));
				Bucket bucket1 =Bucket(this->tamanioBucket, bloque1);
				tabla->reducirTabla();
				bucket1.modificarTamanioDispersion(tabla->obtenertd(tabla->obtener(posTabla2)));
				archivo->escribirBloque(*bucket1.obtenerBloque(),tabla->obtener(posTabla2));
				}
			}else {
			bucket.borrar(key,tabla->obtenertd(posBucket));
			archivo->escribirBloque(*bucket.obtenerBloque(),posBucket);
			}
			this->tabla->guardarTabla(narchivoTabla);
		}	return true;
	} else return false;
}


bool Hash::modificar(Key key){
	if(existe(key)){

		unsigned int posBucket=tabla->obtenerNroBucket(key);
		unsigned int posTabla=tabla->calcularNroBucket(key);
		Bloque* bloque=archivo->leerBloque(posBucket);

		Bucket bucket =Bucket(this->tamanioBucket, bloque);
		Bloque * bloqueviejo=new Bloque(tamanioBucket);
		Bucket bucketnuevo=Bucket(tamanioBucket,bloqueviejo);
		unsigned int nroBloque;
		//saco la clave del bucket para verificar ahora si entra la clave modificada
		bucket.borrar(key,tabla->obtenertd(posBucket));
			//verifico que no desborde
			if (!bucket.desborda(key)){
				bucket.insertar(key,tabla->obtenertd(posBucket));
				archivo->escribirBloque(*bucket.obtenerBloque(),posBucket);
			}else { //no entra ver desbord
				//verifico si tengo que dcuplicar la tabla td= tt
				if (bucket.obtenerTamanioDispersion()==int(tabla->tamanioTabla())){
					tabla->duplicarTabla();
				}
				//busco un bloque libre para insertar
				if(!archivoBLibres->vacio()){

					nroBloque=archivoBLibres->obteneBloque();
					this->archivoBLibres->guardarBloques();
					Bloque* bloqueLibre= new Bloque(tamanioBucket);
					Bucket bucketnuevo=Bucket(tamanioBucket,bloqueLibre);
					tabla->modificar(posTabla,nroBloque);

				}else {//si no hay bloques libres agrego un bloque al final
						archivo->agregarBloquesVacios(1);

						//hasta que esto ande
						nroBloque=archivo->cantBloques-1;
						cantidadBuckets++;
						Bloque* bloque=new Bloque(tamanioBucket);
						bucketnuevo=Bucket(tamanioBucket,bloque);
						archivo->escribirBloque(*bloque,nroBloque);
						tabla->modificar(posTabla,nroBloque);
				}

			unsigned int tam=bucket.obtenerVector().size();
			Bloque * bloqueviejo=new Bloque(tamanioBucket);
			Bucket bucketviejo=Bucket(tamanioBucket,bloqueviejo);

			//reparto lo viejo en los dos bloques nuevos
			for(unsigned int x=0;x<tam;x++){
				Key* keyIns=(bucket.obtenerVector())[x];
				unsigned int posBucketNueva=tabla->obtenerNroBucket(*keyIns);
				//tabla->mostrar();
				if (posBucketNueva==nroBloque){
					bucketnuevo.insertar(*keyIns,tabla->obtenertd(nroBloque));
				}else bucketviejo.insertar(*keyIns,tabla->obtenertd(posBucket));
			}
			//inserto la nueva
			unsigned int posBucketNueva=tabla->obtenerNroBucket(key);
			if (posBucketNueva==nroBloque){
				bucketnuevo.insertar(key,tabla->obtenertd(nroBloque));
			}
			else bucketviejo.insertar(key,tabla->obtenertd(posBucket));
				//escribo en el archivo los bloques
				archivo->escribirBloque(*bucketnuevo.obtenerBloque(),nroBloque);
				archivo->escribirBloque(*bucketviejo.obtenerBloque(),posBucket);
			}
			this->tabla->guardarTabla(narchivoTabla);
			return true;
		} else return false;//no existe la key a modificar
}


string Hash::buscar(Key key){
	if (existe(key)){
		unsigned int posBucket=tabla->obtenerNroBucket(key);
		Bloque* bloque=archivo->leerBloque(posBucket);
		Bucket bucket =Bucket(this->tamanioBucket, bloque);
		return bucket.buscar(key);
	} else return  "";
}


unsigned int Hash::obtenerTamanioBucket(){
	return this->tamanioBucket;
}


unsigned int Hash::obtenerCantidadBuckets(){
	return this->archivo->cantBloques;
}

